package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type Msg struct {
	State, Me, Object, Info, Content string
}
type Room struct {
	Owner, Name  string
	State        string
	ImageMap     []int
	StartTime    time.Time
	CountRemains int
	Members      []string
}

var (
	pushConns = make(map[string]net.Conn)
	members   []Msg
	rooms     []Room
)

func main() {
	go StartPushServer()
	http.HandleFunc("/apis/getonlinenum", http_getOnlineNum)
	http.HandleFunc("/apis/createroom", http_createRoom)
	http.HandleFunc("/apis/getallrooms", http_getAllRooms)
	http.HandleFunc("/apis/joinroom", http_joinRoom)
	http.HandleFunc("/apis/getroominfo", http_getRoomInfo)
	http.HandleFunc("/apis/leaveroom", http_leaveRoom)
	http.HandleFunc("/apis/droproom", http_dropRoom)
	http.HandleFunc("/apis/startgame", http_startGame)
	http.HandleFunc("/apis/submit", http_submit)
	http.HandleFunc("/apis/getnick", http_getnick)
	e := http.ListenAndServe(":8080", nil)
	if e != nil {
		fmt.Println(e)
	}
}
func http_getnick(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:IP
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	for _, v := range members {
		if v.Info == msg.Info {
			ReturnInfo(w, "OK", v.Me)
			return
		}
	}
	ReturnInfo(w, "ERR", "not found")
}
func http_submit(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:roomName,Content:x,Object:y
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	for k, v := range rooms {
		if v.Name == msg.Info {
			if v.State != "gaming" {
				ReturnInfo(w, "ERR", "room is not gaming")
				return
			}
			x64, e := strconv.ParseInt(msg.Content, 10, 32)
			if e != nil {
				fmt.Println(e)
				ReturnInfo(w, "ERR", e.Error())
				return
			}
			y64, e := strconv.ParseInt(msg.Object, 10, 32)
			if e != nil {
				fmt.Println(e)
				ReturnInfo(w, "ERR", e.Error())
				return
			}
			x := int(x64)
			y := int(y64)
			if x > 900 || x < 1 || y > 900 || y < 1 {
				ReturnInfo(w, "ERR", "bad input")
				return
			}
			rooms[k].ImageMap[getIndexOfXY(x, y)]++
			rooms[k].CountRemains--
			if rooms[k].CountRemains < 1 {
				rooms[k].State = "end"
				result := "win"
				if rooms[k].ImageMap[getIndexOfXY(x, y)]%2 == 0 {
					result = "lose"
				}
				sendToAllMembersInRoom(Msg{State: "end", Me: getNickFromMembers(getIPfromAdrs(r.RemoteAddr)), Info: getIPfromAdrs(r.RemoteAddr), Content: result}, k)
				ReturnInfo(w, "OK", "info")
				return
			}
			msg.State = "submit"
			sendToAllMembersInRoom(msg, k)
			ReturnInfo(w, "OK", "info")
			pollingSend(k)
			return
		}
	}
	ReturnInfo(w, "ERR", "submit failed")
}
func getNickFromMembers(ip string) string {
	for _, v := range members {
		if v.Info == ip {
			return v.Me
		}
	}
	return ""
}
func getIndexOfXY(x, y int) int {
	row := x / 300
	colum := y / 300
	fmt.Println(row, colum)
	return colum*3 + row
}
func http_startGame(w http.ResponseWriter, r *http.Request) {
	owner := getIPfromAdrs(r.RemoteAddr)
	for k, v := range rooms {
		if v.Owner == owner {
			rooms[k].State = "gaming"
			rooms[k].StartTime = time.Now()
			sendToAllMembersInRoom(Msg{State: "startGame"}, k)
			ReturnInfo(w, "OK", "info")
			go func() {
				time.Sleep(2 * time.Second)
				pollingSend(k)
			}()
			return
		}
	}
	ReturnInfo(w, "ERR", "no room to start")
}
func pollingSend(roomIndex int) {
	if len(rooms[roomIndex].Members) < 1 {
		fmt.Println("0 member , can't pollingSend")
		return
	}
	round := rooms[roomIndex].CountRemains % len(rooms[roomIndex].Members)
	ip := rooms[roomIndex].Members[round]
	if pushConns[ip] == nil {
		fmt.Println("pushConn is nil")
		return
	}
	b, e := json.Marshal(Msg{State: "yourTurn"})
	if e != nil {
		fmt.Println(e)
		return
	}
	con, ok := pushConns[ip]
	if !ok {
		fmt.Println("pushCons[" + ip + "] doesn't exists")
		return
	}
	_, e = con.Write(b)
	if e != nil {
		fmt.Println("write failed", e.Error())
		return
	}
	_, e = con.Write([]byte("\n"))
	if e != nil {
		fmt.Println("wriet2 failed", e)
		return
	}
	fmt.Println("writed polling")
}
func http_dropRoom(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:roomName
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	for k, v := range rooms {
		if v.Name == msg.Info {
			if v.Owner != getIPfromAdrs(r.RemoteAddr) {
				ReturnInfo(w, "ERR", "permission denied")
				return
			}
			msg.State = "dropRoom"
			sendToAllMembersInRoom(msg, k)
			rooms = append(rooms[:k], rooms[k+1:]...)
			ReturnInfo(w, "OK", "info")
			return
		}
		break
	}
	ReturnInfo(w, "ERR", "drop Room failed")
}
func http_leaveRoom(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:roomName
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	for k, v := range rooms {
		if v.Name == msg.Info {
			for k1, v1 := range v.Members {
				if v1 == getIPfromAdrs(r.RemoteAddr) {
					rooms[k].Members = append(rooms[k].Members[:k1], rooms[k].Members[k1+1:]...)
					msg.State = "leaveRoom"
					sendToAllMembersInRoom(msg, k)
					ReturnInfo(w, "OK", "info")
					return
				}
			}
			break
		}
	}
	ReturnInfo(w, "ERR", "no such room or member")
}
func http_getRoomInfo(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:roomName
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	var backData struct {
		State, Info string
		Data        Room
	}
	for _, v := range rooms {
		if v.Name == msg.Info {
			backData.State = "OK"
			backData.Info = strconv.FormatInt(int64(time.Since(v.StartTime).Seconds()), 10)
			backData.Data = v
			ReturnData(w, backData)
			return
		}
	}
	ReturnInfo(w, "ERR", "getRoomInfo failed")
}
func http_joinRoom(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:roomName , Me:nick
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	for k, v := range rooms {
		if v.Name == msg.Info {
			if v.State != "waiting" {
				ReturnInfo(w, "ERR", "Room is gaming")
				return
			}
			rooms[k].Members = append(rooms[k].Members, getIPfromAdrs(r.RemoteAddr))
			msg.State = "joinRoom"
			sendToAllMembersInRoom(msg, k)
			ReturnInfo(w, "OK", "")
			return
		}
	}
	ReturnInfo(w, "ERR", "join room failed")
}
func http_getOnlineNum(w http.ResponseWriter, r *http.Request) {
	ReturnInfo(w, "OK", strconv.FormatInt(int64(len(members)), 10))
}
func http_createRoom(w http.ResponseWriter, r *http.Request) {
	msg, e := readMsgFromRequest(r) //Info:roomName
	if e != nil {
		fmt.Println(e)
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	room := Room{
		Owner:        getIPfromAdrs(r.RemoteAddr),
		Name:         msg.Info,
		State:        "waiting",
		ImageMap:     []int{0, 0, 0, 0, 0, 0, 0, 0, 0},
		CountRemains: 9,
	}
	room.Members = append(room.Members, getIPfromAdrs(r.RemoteAddr))
	rooms = append(rooms, room)
	sendToAllMembers(Msg{State: "createRoom", Me: getIPfromAdrs(r.RemoteAddr), Info: msg.Info})
	ReturnInfo(w, "OK", "")
	fmt.Println("room ", msg.Info, "created")
}
func http_getAllRooms(w http.ResponseWriter, r *http.Request) {
	var backData struct {
		State string
		Data  []Room
	}
	backData.State = "OK"
	backData.Data = rooms
	b, e := json.Marshal(backData)
	if e != nil {
		ReturnInfo(w, "ERR", e.Error())
		return
	}
	w.Write(b)
}
func readMsgFromRequest(r *http.Request) (Msg, error) {
	msg := Msg{}
	b, e := ioutil.ReadAll(r.Body)
	if e != nil {
		fmt.Println(e)
		return msg, e
	}
	e = json.Unmarshal(b, &msg)
	return msg, e
}
func StartPushServer() {
	adrs, e := net.ResolveTCPAddr("tcp", ":9090")
	if e != nil {
		fmt.Println(e)
		return
	}
	l, e := net.ListenTCP("tcp", adrs)
	if e != nil {
		fmt.Println(e)
		return
	}
	for {
		con, e := l.Accept()
		if e != nil {
			fmt.Println(e)
			break
		}
		go handlePushConn(con)
	}
}
func handlePushConn(con net.Conn) {
	fmt.Println("new connection", getIPfromAdrs(con.RemoteAddr().String()))
	defer con.Close()
	defer func() {
		delete(pushConns, getIPfromAdrs(con.RemoteAddr().String()))
		removeMember(getIPfromAdrs(con.RemoteAddr().String()))
		removeMemberFromRooms(getIPfromAdrs(con.RemoteAddr().String()))
		sendToAllMembers(Msg{State: "offline", Info: getIPfromAdrs(con.RemoteAddr().String())})
	}()
	br := bufio.NewReader(con)
	for {
		b, _, e := br.ReadLine()
		if e != nil {
			fmt.Println(e)
			break
		}
		fmt.Println("received:", string(b))
		msg := Msg{}
		e = json.Unmarshal(b, &msg)
		if e != nil {
			fmt.Println(e.Error())
			break
		}
		if msg.State != "connected" {
			break
		}
		msg.Info = getIPfromAdrs(con.RemoteAddr().String())
		msg.State = "online"
		pushConns[getIPfromAdrs(con.RemoteAddr().String())] = con
		members = append(members, msg)
		sendToAllMembers(msg)
		fmt.Println("member connected:", getIPfromAdrs(con.RemoteAddr().String()))
	}
}
func removeMember(ip string) {
	for k, v := range members {
		if v.Info == ip {
			members = append(members[:k], members[k+1:]...)
			fmt.Println("member ", ip, "removed")
			return
		}
	}
	fmt.Println("didn't remove anyone")
}
func removeMemberFromRooms(ip string) {
	for k, v := range rooms {
		for k1, v1 := range v.Members {
			if v1 == ip {
				rooms[k].Members = append(rooms[k].Members[:k1], rooms[k].Members[k1+1:]...)
				fmt.Println("remove member", ip, "in room", v.Name)
				return
			}
		}
	}
	fmt.Println("didn't remove member in rooms")
}
func sendToAllMembers(msg Msg) {
	b, e := json.Marshal(msg)
	if e != nil {
		fmt.Println("sendToAll failed:", e)
		return
	}
	for _, v := range pushConns {
		v.Write(b)
		v.Write([]byte("\n"))
	}
}
func sendToAllMembersInRoom(msg Msg, roomIndex int) {
	b, e := json.Marshal(msg)
	if e != nil {
		fmt.Println("sendToAllInRoom failed", e)
		return
	}
	if len(rooms) < roomIndex {
		fmt.Println("roomIndex out of bound")
		return
	}
	for _, v := range rooms[roomIndex].Members {
		k, ok := pushConns[v]
		if ok {
			k.Write(b)
			k.Write([]byte("\n"))
		} else {
			fmt.Println("no such pushConn of", v)
		}
	}
}
func DoRequest(url, data string) (string, error) {
	sr := strings.NewReader(data)
	rp, e := http.Post(url, "text/plain", sr)
	if e != nil {
		fmt.Println(e)
		return "", e
	}
	defer rp.Body.Close()
	back, e := ioutil.ReadAll(rp.Body)
	if e != nil {
		fmt.Println(e)
		return "", e
	}
	return string(back), nil
}
func ReturnInfo(w http.ResponseWriter, state, info string) {
	b, e := json.Marshal(Msg{State: state, Info: info})
	if e != nil {
		fmt.Println(e)
		return
	}
	w.Write(b)
}
func ReturnData(w http.ResponseWriter, data interface{}) {
	b, e := json.Marshal(data)
	if e != nil {
		fmt.Println(e)
		return
	}
	w.Write(b)
}
func getIPfromAdrs(adrs string) string {
	for k, v := range adrs {
		if string(v) == ":" {
			return adrs[:k]
		}
	}
	return adrs
}
